<?php
if (! defined('IN_SITE')) {
	exit('invalid request');
}

/**
 * 针对淘宝网的网页抓取工具。
 *
 * 实现要点：
 * 		1. 请求过程中保存 cookies，以减少被判定为机器人的可能性。
 * 		2. 必要时通过 Onion Router 变换 IP 来规避服务器的防刷机制。
 */

class BasicFetcher {

	public $headers;
	public $cookieFile;
	public $useOnionRouter;
	public $lastResp;

	public function __construct($name = 'taobao')
	{
		$this->name = safeSimpleName($name);
		$this->headers = array(
			'Pragma: no-cache',
			'Cache-Control: no-cache',
			'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
			'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36',
			'Accept-Language: zh-CN,zh;q=0.8,en;q=0.6',
			'Connection: close',
		);
		$this->cookieFile = "{$GLOBALS['config']['log_path']}{$this->name}_fetcher_cookies.txt";
		$this->useOnionRouter = false;
		$this->lastResp = null;
	}

	public function get($url, $newSession = false)
	{
		if ($this->useOnionRouter && $newSession) {
			// 如果启用了 onion router，则在新 session 的时候先切换 IP
			$this->changeIP();
		}

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_FAILONERROR, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->useOnionRouter ? 100 : 30);

		if (!empty($this->headers)) {
			curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
		}
		if ($newSession) {
			// 新 session 的首次请求，保存得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookieFile);
			curl_setopt($ch, CURLOPT_COOKIESESSION, true);
		} else {
			// 后续的请求，使用前面得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookieFile);
		}
		if ($this->useOnionRouter) {
			curl_setopt($ch, CURLOPT_PROXY, '106.186.25.42:9050');
			curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
		}
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

		$resp = curl_exec($ch);
		$this->lastResp = array(
			'errno'		=> curl_errno($ch),
			'status'	=> curl_getinfo($ch, CURLINFO_HTTP_CODE),
			'resp'		=> $resp,
		);
		$html = false;
		if ($this->lastResp['errno']) {
			$this->lastResp['error'] = curl_error($ch);
		} else {
			if ($this->lastResp['status'] === 200) {
				$html = $resp;
			}
		}
		curl_close($ch);
		return $html;
	}

	public function getLocation($url, $newSession = false)
	{
		if ($this->useOnionRouter && $newSession) {
			// 如果启用了 onion router，则在新 session 的时候先切换 IP
			$this->changeIP();
		}

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);

		curl_setopt($ch, CURLOPT_FAILONERROR, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->useOnionRouter ? 100 : 30);
		if (!empty($this->headers)) {
			curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
		}
		if ($newSession) {
			// 新 session 的首次请求，保存得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookieFile);
			curl_setopt($ch, CURLOPT_COOKIESESSION, true);
		} else {
			// 后续的请求，使用前面得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookieFile);
		}
		if ($this->useOnionRouter) {
			curl_setopt($ch, CURLOPT_PROXY, '106.186.25.42:9050');
			curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
		}
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
		curl_setopt($ch, CURLOPT_HEADER, true);

		$resp = curl_exec($ch);
		$this->lastResp = array(
			'errno'		=> curl_errno($ch),
			'status'	=> curl_getinfo($ch, CURLINFO_HTTP_CODE),
			'resp'		=> $resp,
		);
		if ($this->lastResp['errno']) {
			$this->lastResp['error'] = curl_error($ch);
		}

		$location = false;
		$lines = explode("\r\n", $resp);
		foreach ($lines as $line) {
			if (empty($line)) break;
			if (strBeginsWith($line, 'Location: ', true)) {
				$location = substr($line, 10);
				break;
			}
		}
		curl_close($ch);
		return $location;
	}

	public function post($url, $data, $newSession = false)
	{
		if ($this->useOnionRouter && $newSession) {
			// 如果启用了 onion router，则在新 session 的时候先切换 IP
			$this->changeIP();
		}

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_FAILONERROR, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->useOnionRouter ? 100 : 30);

		if (!empty($this->headers)) {
			curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
		}
		if ($newSession) {
			// 新 session 的首次请求，保存得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookieFile);
			curl_setopt($ch, CURLOPT_COOKIESESSION, true);
		} else {
			// 后续的请求，使用前面得到的 cookies
			curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookieFile);
		}
		if ($this->useOnionRouter) {
			curl_setopt($ch, CURLOPT_PROXY, '106.186.25.42:9050');
			curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
		}
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

		$resp = curl_exec($ch);
		$this->lastResp = array(
			'errno'		=> curl_errno($ch),
			'status'	=> curl_getinfo($ch, CURLINFO_HTTP_CODE),
			'resp'		=> $resp,
		);
		$html = false;
		if ($this->lastResp['errno']) {
			$this->lastResp['error'] = curl_error($ch);
		} else {
			if ($this->lastResp['status'] === 200) {
				$html = $resp;
			}
		}
		curl_close($ch);
		return $html;
	}

	public function dumpLastResp($title)
	{
		$dump = array("[{$title}]");
		if ($this->lastResp['errno']) {
			$dump[] = "ERROR [{$this->lastResp['errno']}:{$this->lastResp['error']}]";
		}
		$dump[] = "STATUS [{$this->lastResp['status']}]";
		$dump[] = "resp [{$this->lastResp['resp']}]";
		return implode("\r\n", $dump);
	}

	public function changeIP()
	{
		// 远程登录 Onion Router 服务器
		$sess = ssh2_connect('106.186.25.42', 22);
		ssh2_auth_password($sess, 'torflip', 'torflip2015');

		// 不用实际执行什么命令，torflip 帐号登录后会自动执行脚本程序切换 tor curcuit，然后直接退出
		$stream = ssh2_exec($sess, '');
		if (empty($stream)) {
			$output = '';
		} else {
			stream_set_blocking($stream, true);
			$output = trim(stream_get_contents($stream));
			fclose($stream);
		}
		unset($sess);
	}

}

class TaobaoFetcher extends BasicFetcher {

	public function __construct()
	{
		parent::__construct();
	}

}
